package com.whf.android.jar.web.client;

import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
import android.view.KeyEvent;
import android.webkit.CookieManager;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.annotation.Nullable;

import com.blankj.utilcode.util.LogUtils;
import com.whf.android.jar.app.Latte;
import com.whf.android.jar.base.delegate.IPageLoadListener;
import com.whf.android.jar.util.log.LatteLogger;
import com.whf.android.jar.util.storage.LattePreference;
import com.whf.android.jar.web.WebDelegate;
import com.whf.android.jar.web.route.Router;

import org.jsoup.internal.StringUtil;

import java.io.IOException;


/**
 * 主要帮助WebView处理各种通知、请求事件
 *
 * @author hf
 * @version 1.0.5
 */
public class WebViewClientImpl extends WebViewClient {

    private final static String INJECTION_TOKEN = "token";

    private final WebDelegate DELEGATE;
    private IPageLoadListener mIPageLoadListener = null;
    private boolean isHandleWebUrl = true;

    public WebViewClientImpl(WebDelegate delegate) {
        this.DELEGATE = delegate;
    }

    /**
     * @param listener:设置加载进度条
     */
    public void setPageLoadListener(IPageLoadListener listener) {
        this.mIPageLoadListener = listener;
    }

    /**
     * @param handleWebUrl:设置是否接管控制网页的打电话、a标签等操作
     */
    public void setHandleWebUrl(boolean handleWebUrl) {
        this.isHandleWebUrl = handleWebUrl;
    }


    /**
     * 加载网页需要重定向的时候回调
     * <p>
     * 当加载的网页需要重定向的时候就会回调这个函数告知我们应用程序是否需要接管控制网页加载，如果应用程序接管，
     * 并且return true意味着主程序接管网页加载，如果返回false让webView自己处理。
     * </p>
     * 参数说明：
     *
     * @param view 接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *             MyAndroidWebViewClient())，即是这个webView。
     * @param url  即将要被加载的url
     * @return true 当前应用程序要自己处理这个url， 返回false则不处理。 注："post"请求方式不会调用这个回调函数
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        LatteLogger.d("shouldOverrideUrlLoading", url);
        return Router.getInstance().handleWebUrl(DELEGATE, url, isHandleWebUrl);
    }

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        super.onReceivedSslError(view, handler, error);
    }

    /**
     * 开始加载网络
     * <p> 当内核开始加载访问的url时，会通知应用程序，对每个main frame
     * 这个函数只会被调用一次，页面包含iframe或者Fragment 不会另外调用一次onPageStarted，
     * 当网页内内嵌的frame 发生改变时也不会调用onPageStarted。
     * </p>
     * 参数说明：
     *
     * @param view    接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *                MyAndroidWebViewClient())，即是这个webView。
     * @param url     即将要被加载的url
     * @param favicon 如果这个favicon已经存储在本地数据库中，则会返回这个网页的favicon，否则返回为null。
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        if (mIPageLoadListener != null) {
            mIPageLoadListener.showLoading();
        }
    }

    //获取浏览器cookie
    private void syncCookie() {
        final CookieManager manager = CookieManager.getInstance();
        /*
          注意，这里的Cookie和API请求的Cookie是不一样的，这个在网页不可见
         */
        final String webHost = Latte.getWebServer();
        if (webHost != null) {
            if (manager.hasCookies()) {
                final String cookieStr = manager.getCookie(webHost);
                if (!StringUtil.isBlank(cookieStr)) {
                    LattePreference.addCustomAppProfile("cookie", cookieStr);
                }
            }
        }
    }


    /**
     * 网页加载完成回调
     *
     * <p> 当内核加载完当前页面时会通知我们的应用程序，这个函数只有在main
     * frame情况下才会被调用，当调用这个函数之后，渲染的图片不会被更新，如果需要获得新图片的通知可以使用@link
     * WebView.PictureListener#onNewPicture。
     * </p>
     * 参数说明：
     *
     * @param view 接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *             MyAndroidWebViewClient())，即是这个webView。
     * @param url  即将要被加载的url
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        syncCookie();
        if (mIPageLoadListener != null) {
            mIPageLoadListener.hideLoading();
        }
    }

    /**
     * 加载Url资源回调
     * <p>
     * onLoadResource 通知应用程序WebView即将加载url 制定的资源
     * <p>
     * 参数说明：
     *
     * @param view 接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *             MyAndroidWebViewClient())，即是这个webView。
     * @param url  即将加载的url 资源
     */
    @Override
    public void onLoadResource(WebView view, String url) {
        super.onLoadResource(view, url);
        LatteLogger.i("onLoadResource:加载资源指定的网址");
    }


    /**
     * shouldInterceptRequest
     * 通知应用程序内核即将加载url制定的资源，应用程序可以返回本地的资源提供给内核，若本地处理返回数据，内核不从网络上获取数据。
     * <p>
     * 参数说明：
     *
     * @param view    接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *                MyAndroidWebViewClient())，即是这个webView。
     * @param request raw url 制定的资源
     * @return 返回WebResourceResponse包含数据对象，或者返回null
     */
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        WebResourceResponse response = super.shouldInterceptRequest(view, request);
        String url = request.getUrl().toString();
        if (url.contains(INJECTION_TOKEN)) {
            String assetPath = url.substring(url.indexOf(INJECTION_TOKEN) + INJECTION_TOKEN.length());
            try {
                response = new WebResourceResponse("application/javascript", "UTF8",
                        Latte.getActivity().getAssets().open(assetPath)
                );
            } catch (IOException e) {
                LogUtils.e(e.getMessage()); // Failed to load asset file
            }
        }
        return response;
    }

    /**
     * 访问地址错误回调
     * <p>
     * 当浏览器访问制定的网址发生错误时会通知我们应用程序 参数说明：
     * </p>
     *
     * @param view    接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *                MyAndroidWebViewClient())，即是这个webView。
     * @param request 描述错误的信息
     * @param error   当前访问失败的url，注意并不一定是我们主url
     */
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        super.onReceivedError(view, request, error);
        LatteLogger.i("onReceivedError:访问地址错误回调");
    }

    /**
     * 如果浏览器需要重新发送POST请求，可以通过这个时机来处理。默认是不重新发送数据。
     * <p>
     * 参数说明
     *
     * @param view     接收WebViewClient的webView
     * @param inResend 浏览器不需要重新发送的参数
     * @param resend   浏览器需要重新发送的参数
     */
    @Override
    public void onFormResubmission(WebView view, Message inResend, Message resend) {
        super.onFormResubmission(view, inResend, resend);
        LatteLogger.i("onFormResubmission:发送POST请求");
    }

    /**
     * 更新数据库
     * <p>
     * 通知应用程序可以将当前的url存储在数据库中，意味着当前的访问url已经生效并被记录在内核当中。这个函数在网页加载过程中只会被调用一次。
     * 注意网页前进后退并不会回调这个函数。
     * <p>
     * 参数说明：
     *
     * @param view     接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *                 MyAndroidWebViewClient())，即是这个webView。
     * @param url      当前正在访问的url
     * @param isReload 如果是true 这个是正在被reload的url
     */
    @Override
    public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
        super.doUpdateVisitedHistory(view, url, isReload);
        LatteLogger.i("doUpdateVisitedHistory:更新数据库");
    }

    /**
     * auth请求回调
     * <p> 通知应用程序WebView接收到了一个Httpauth的请求，
     * 应用程序可以使用supplied 设置webView的响应请求。默认行为是cancel 本次请求。
     * <p>
     * 参数说明：
     *
     * @param view    接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *                MyAndroidWebViewClient())，即是这个webView。
     * @param handler 用来响应WebView请求的对象
     * @param host    请求认证的host
     * @param realm   认真请求所在的域
     */
    @Override
    public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
        super.onReceivedHttpAuthRequest(view, handler, host, realm);
        LatteLogger.i("onReceivedHttpAuthRequest:auth请求回调");
    }

    /**
     * 按键处理回调
     * <p>
     * 提供应用程序同步一个处理按键事件的机会，菜单快捷键需要被过滤掉。如果返回true，webView不处理该事件，如果返回false，
     * webView会一直处理这个事件，因此在view 链上没有一个父类可以响应到这个事件。默认行为是return false；
     * <p>
     * 参数说明：
     *
     * @param view  接收WebViewClient的那个实例，前面看到webView.setWebViewClient(new
     *              MyAndroidWebViewClient())，即是这个webView。
     * @param event 键盘事件名
     * @return 如果返回true, 应用程序处理该时间，返回false 交有webView处理。
     */
    @Override
    public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
        LatteLogger.i("shouldOverrideKeyEvent:按键处理回调");
        return super.shouldOverrideKeyEvent(view, event);
    }

    /**
     * 通知应用程序webView 要被scale。应用程序可以处理改事件，比如调整适配屏幕。
     */
    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale) {
        super.onScaleChanged(view, oldScale, newScale);
        LatteLogger.i("onScaleChanged:WebView比例改变");
    }

    /**
     * 通知应用程序有个自动登录的帐号过程
     * <p>
     * 参数说明：
     *
     * @param view    请求登陆的webView
     * @param realm   账户的域名，用来查找账户。
     * @param account 一个可选的账户，如果是null 需要和本地的账户进行check， 如果是一个可用的账户，则提供登录。
     * @param args    验证制定参数的登录用户
     */
    @Override
    public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
        super.onReceivedLoginRequest(view, realm, account, args);
        LatteLogger.i("onScaleChanged:WebView比例改变");
    }
}
